/*
trace -a <routetrace port> -b <src hostname> -c <src port>
	  -d <dest hostname>   -e <dest port>    -f <debug option>
*/

#include <iostream>
#include <netdb.h>      // gethostbyname
#include <stdlib.h>     // EXIT_FAILURE
#include <string.h>     // strcmp

// inet_ntoa
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "packet.h"

using namespace std;

#define NUM_PARAMS 13

#define ROUTETRACE_PORT "-a"
#define SRC_HOSTNAME    "-b"
#define SRC_PORT        "-c"
#define DEST_HOSTNAME   "-d"
#define DEST_PORT       "-e"
#define DEBUG_OPTION    "-f"

#define IS_BLOCKING true

unsigned short routetrace_port, src_port, dest_port;
string src_hostname, dest_hostname;
int debug_option;

void parse_cmd_line(char** argv) {
	// Skip program name
    for (int i = 1; i < NUM_PARAMS; i += 2) {
        if (strcmp(argv[i], ROUTETRACE_PORT) == 0) {
            routetrace_port = (unsigned short) atoi(argv[i+1]);
        }
        else if (strcmp(argv[i], SRC_HOSTNAME) == 0) {
            src_hostname = (string) argv[i+1];
        }
        else if (strcmp(argv[i], SRC_PORT) == 0) {
            src_port = (unsigned short) atoi(argv[i+1]);
        }
        else if (strcmp(argv[i], DEST_HOSTNAME) == 0) {
            dest_hostname = (string) argv[i+1];
        }
        else if (strcmp(argv[i], DEST_PORT) == 0) {
            dest_port = (unsigned short) atoi(argv[i+1]);
        }
		else if (strcmp(argv[i], DEBUG_OPTION) == 0) {
			debug_option = atoi(argv[i+1]);
		}
        else {
            cout << "Invalid parameter: " << argv[i] << endl;
            exit(EXIT_FAILURE);
        }
	}
}

void print_args() {
	cout << "Routetrace port: " << routetrace_port << endl;
	cout << "Source hostname: " << src_hostname    << endl;
	cout << "Source port:     " << src_port        << endl;
	cout << "Dest hostname:   " << dest_hostname   << endl;
	cout << "Dest port:       " << dest_port       << endl;
	cout << "Debug option:    " << debug_option    << endl;
}

int main(int argc, char** argv) {
	if (argc != NUM_PARAMS) {
        cout << "Usage: trace -a <routetrace port> -b <src hostname> ";
        cout << "-c <src port> -d <dest hostname> -e <dest port> ";
        cout << "-f <debug option>" << endl;
        exit(EXIT_FAILURE);
    }
    
    // 1. Get src and deest ip and port from cmd line
    parse_cmd_line(argv);
    print_args();
    
    // Create socket
    int sockfd;
    struct sockaddr_in trace_addr, src_addr, recv_addr;
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("Socket construction failed");
        exit(EXIT_FAILURE);
    }
    
    // Trace receives data from anyone on specified port
    bzero(&trace_addr, sizeof(trace_addr));
    trace_addr.sin_family      = AF_INET;
    trace_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    trace_addr.sin_port        = htons(routetrace_port);
    
    if (bind(sockfd,(struct sockaddr*)&trace_addr,sizeof(trace_addr))==-1) {
        perror("Bind");
        exit(EXIT_FAILURE);
    }
    
    // Get routetrace IP
    char routetrace_hostname[50]; 
    gethostname(routetrace_hostname, 50);
	struct hostent *h = gethostbyname(routetrace_hostname);
	string routetrace_ip_dot=inet_ntoa(*((struct in_addr *)h->h_addr_list[0]));
	unsigned long routetrace_ip = inet_addr(routetrace_ip_dot.c_str());
    
    cout << "My hostname: " << routetrace_hostname << endl;
    cout << "My IP: " << routetrace_ip_dot << " - " << routetrace_ip << endl;
    
    // Get src IP
    h = gethostbyname(src_hostname.c_str());
	string src_ip_dot = inet_ntoa(*((struct in_addr *)h->h_addr_list[0]));
	unsigned long src_ip = inet_addr(src_ip_dot.c_str());
	
	cout << "Src hostname:  " << src_hostname << endl;
	cout << "Src IP:  " << src_ip_dot << " - " << src_ip << endl;
	
	// Get dest IP
    h = gethostbyname(dest_hostname.c_str());
	string dest_ip_dot = inet_ntoa(*((struct in_addr *)h->h_addr_list[0]));
    unsigned long dest_ip = inet_addr(dest_ip_dot.c_str());
    
    cout << "Dest hostname: " << dest_hostname << endl;
    cout << "Dest IP: " << dest_ip_dot << " - " << dest_ip << endl;
    
    // Trace sends data to src
    bzero(&src_addr, sizeof(src_addr));
    src_addr.sin_family      = AF_INET;
    src_addr.sin_addr.s_addr = src_ip;
    src_addr.sin_port        = htons(src_port);
    
    // 2. Set ttl to 0
    unsigned long ttl = 0;
    
    while (1) {
		// 3. Send routetrace pkt to src with fields:
		// 'T', TTL, routetrace IP, routetrace Port, Dest IP, Dest Port
		Packet p = Packet(TRACE, ttl, routetrace_ip, routetrace_port,
			dest_ip, dest_port, 0, "");
		
		p.send_packet(sockfd, src_addr);
		
		if (debug_option) {
			cout << "Sent packet:" << endl;
			p.print_packet();
		}
	
		// 4. Wait for response
		Packet *recv = Packet::receive_packet(sockfd, &recv_addr, 512,
			IS_BLOCKING);
	
		if (debug_option) {
			cout << "Received packet:" << endl;
			recv->print_packet();
		}
	
		// 5. Print out responders IP and port
		cout << "Responders IP:   " << recv->get_src_ip()   << endl;
		cout << "Responders Port: " << recv->get_src_port() << endl;
	
		// 6. If received src ip and port == dest ip and port from cmd line
		if (recv->get_src_ip() == dest_ip && 
			recv->get_src_port() == dest_port) {
			// Terminate
			exit(EXIT_SUCCESS);
		}
		else {
			// TTL++, goto 3
			ttl++;
		}
	}
}
